#include "HDRDemoUtils.hpp"
#include "ProjectionMatrix.hpp"
#include "HDRDemoConstants.hpp"
#include "Mathematics/MMatrix4.hpp"
#include "Mathematics/MVector3D.hpp"
#include <fstream>
#include <time.h>

using namespace std;

bool HDRDemoUtils::readTextFile(string* pTextContainer, string fileName)
{
	bool bResult = false;

	ifstream shaderFile;
	int iFileSize;
	char* pBuff = NULL;

	shaderFile.open(fileName.c_str());
	shaderFile.seekg(0, ios::end);
	
	iFileSize = shaderFile.tellg();
	pBuff = new char[iFileSize+1];

	if(pBuff == NULL)
		return bResult;

	memset(pBuff, 0, iFileSize+1);

	shaderFile.seekg(0, ios::beg);
	shaderFile.read(pBuff, iFileSize);
	(*pTextContainer) = "";
	pTextContainer->append(pBuff);
	shaderFile.close();
	delete[] pBuff;

	bResult = true;
	return bResult;
}

bool HDRDemoUtils::textureForShader(const GLhandleARB& shader, string texName, const int& iTextID)
{
	bool bResult = false;
	GLint texLoc = -1;

	ExtensionsLoader::glUseProgramObjectARB(shader);
	texLoc = ExtensionsLoader::glGetUniformLocationARB(shader, texName.c_str());

	if(texLoc < 0)
		return bResult;

	ExtensionsLoader::glUniform1iARB(texLoc, iTextID);

	bResult = true;
	return bResult;
}

bool HDRDemoUtils::projMatrixForShader(const GLhandleARB& shader, string matrixName, ProjectionMatrix* pProjMatrix)
{
	bool bResult = false;

	ExtensionsLoader::glUseProgramObjectARB(shader);

	GLint matrixLoc = -1;
	GLfloat *pProj = new GLfloat[MATRIX_CELLS];

	if(pProj == NULL)
		return bResult;

	MMatrix4 mat = pProjMatrix->GetProjectionMatrix();

	pProj[0] = mat.m_matrix[0]; pProj[1] = mat.m_matrix[1]; pProj[2] = mat.m_matrix[2]; pProj[3] = mat.m_matrix[3];
	pProj[4] = mat.m_matrix[4]; pProj[5] = mat.m_matrix[5]; pProj[6] = mat.m_matrix[6]; pProj[7] = mat.m_matrix[7];
	pProj[8] = mat.m_matrix[8]; pProj[9] = mat.m_matrix[9]; pProj[10] = mat.m_matrix[10]; pProj[11] = mat.m_matrix[11];
	pProj[12] = mat.m_matrix[12]; pProj[13] = mat.m_matrix[13]; pProj[14] = mat.m_matrix[14]; pProj[15] = mat.m_matrix[15];

	matrixLoc = ExtensionsLoader::glGetUniformLocationARB(shader, matrixName.c_str());

	if(matrixLoc < 0)
		return bResult;

	ExtensionsLoader::glUniformMatrix4fvARB(matrixLoc, 1, GL_FALSE, pProj);

	//delete[] pProj;

	bResult = true;
	return bResult;
}

bool HDRDemoUtils::genRandomVectors3D(float** ppRandVectors, const unsigned int& iVecCount, bool bOptimizeForTex)
{
	bool bResult = false;
	float fShift = RAND_MAX/2.0f;
	float* pRandVec = NULL;
	MVector3D vec;

	if(iVecCount <= 0)
		return bResult;

	srand(static_cast<unsigned int>(time(NULL)));

	(*ppRandVectors) = new float[iVecCount*_3DIMMS];

	if((*ppRandVectors) == NULL)
		return bResult;

	pRandVec = (*ppRandVectors);

	int iOffset = 0;
	for(unsigned int i = 0; i < iVecCount; ++i)
	{
		vec.x = static_cast<float>(rand()) - fShift;
		vec.y = static_cast<float>(rand()) - fShift;
		vec.z = static_cast<float>(rand()) - fShift;

		vec.Normalize();

		if(bOptimizeForTex)
		{
			//! Light vector is normalized and then trasnformed from scope [-1,1] to scope [0,1].
			//! It is done by transformation to [0,2] by adding 1.0 and thereafter dividing by 2.
			//! Inverse tranformations should be done it two places: inside of shader before using light vectors
			//! and before building MODELVIEW matrices for lights while rendering shadow maps.
			//! It must be secured that modelview matrices in shader and during shadow maps rendering are the same.
			//! Since in modelview matrices target and eye positions are needed, light vector should be multiplied by
			//! some constant (f.e. 10.0). This will put all eye positions on sphere with radius equal to used constant.

			iOffset = i*_3DIMMS;

			pRandVec[iOffset]   = (vec.x+1.0f) * 0.5f;
			pRandVec[iOffset+1] = (vec.y+1.0f) * 0.5f;
			pRandVec[iOffset+2] = (vec.z+1.0f) * 0.5f;
		}
	}


	bResult = true;
	return bResult;
}

bool HDRDemoUtils::lights2Texture(float* pLights, const unsigned int& iLightsCount, GLuint* pTexID)
{
	bool bResult = false;
	int iWidth, iHeight;

	if(pLights == NULL || iLightsCount <= 0)
		return bResult;

	glGenTextures(1, pTexID);

	//TODO: test this safety check for valid ID
	if(static_cast<int>(*pTexID) < 0)
		return bResult;

	iWidth = iHeight = 0;
	HDRDemoUtils::findOptimalPower2Size(iLightsCount, &iWidth, &iHeight);

	glBindTexture(GL_TEXTURE_2D, (*pTexID));
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16, iWidth, iHeight, 0, GL_RGB, GL_FLOAT, pLights);

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	bResult = true;
	return bResult;
}

void HDRDemoUtils::findOptimalPower2Size(const int& iTotalSize, int* pOptWidth, int* pOptHeight)
{
	int iCurrSize = MAX_TEX_WIDTH * MAX_TEX_HEIGHT;
	int iTempSize, iTempWidth, iTempHeight;

	iTempSize = iTempWidth = iTempHeight = 0;

	for(int i = 1; i < MAX_TEX_WIDTH; ++i)
	{
		for(int j = 1; j < MAX_TEX_HEIGHT; ++j)
		{
			iTempSize = i*j;
			if(iTempSize >= iTotalSize && iTempSize < iCurrSize)
			{
				iCurrSize = iTempSize;
				iTempWidth = i;
				iTempHeight = j;
			}
		}
	}

	(*pOptWidth) = iTempWidth;
	(*pOptHeight) = iTempHeight;
}
//EOF
